package zutils

import (
	"context"
	"errors"
	"fmt"
	ormlog "gorm.io/gorm/logger"
	"time"

	"gorm.io/gorm/utils"
)

// New initialize Gormlogger
func LogNew(writer ormlog.Writer, config ormlog.Config) ormlog.Interface {
	var (
		infoStr      = "%s\n[info] "
		warnStr      = "%s\n[warn] "
		errStr       = "%s\n[error] "
		traceStr     = "%s\n[%.3fms] [rows:%v] %s"
		traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
		traceErrStr  = "%s %s\n[%.3fms] [rows:%v] %s"
	)

	if config.Colorful {
		infoStr = ormlog.Green + "%s\n" + ormlog.Reset + ormlog.Green + "[info] " + ormlog.Reset
		warnStr = ormlog.BlueBold + "%s\n" + ormlog.Reset + ormlog.Magenta + "[warn] " + ormlog.Reset
		errStr = ormlog.Magenta + "%s\n" + ormlog.Reset + ormlog.Red + "[error] " + ormlog.Reset
		traceStr = ormlog.Green + "%s\n" + ormlog.Reset + ormlog.Yellow + "[%.3fms] " + ormlog.BlueBold + "[rows:%v]" +
			ormlog.Reset + " %s"
		traceWarnStr = ormlog.Green + "%s " + ormlog.Yellow + "%s\n" + ormlog.Reset + ormlog.RedBold + "[%.3fms] " +
			ormlog.Yellow + "[rows:%v]" + ormlog.Magenta + " %s" + ormlog.Reset
		traceErrStr = ormlog.RedBold + "%s " + ormlog.MagentaBold + "%s\n" + ormlog.Reset + ormlog.Yellow + "[%.3fms] " +
			ormlog.BlueBold + "[rows:%v]" + ormlog.Reset + " %s"
	}

	return &Gormlogger{
		Writer:       writer,
		Config:       config,
		infoStr:      infoStr,
		warnStr:      warnStr,
		errStr:       errStr,
		traceStr:     traceStr,
		traceWarnStr: traceWarnStr,
		traceErrStr:  traceErrStr,
	}
}

func (l *Gormlogger) SetSlowThreshold(slowTime int64)  {
	l.Config.SlowThreshold = time.Duration(slowTime) * time.Millisecond
}

func (l *Gormlogger) SetLogLevel(level ormlog.LogLevel)  {
	l.Config.LogLevel = level
}


type Gormlogger struct {
	ormlog.Writer
	ormlog.Config
	infoStr, warnStr, errStr            string
	traceStr, traceErrStr, traceWarnStr string
}

// LogMode log mode
func (l *Gormlogger) LogMode(level ormlog.LogLevel) ormlog.Interface {
	newlogger := *l
	newlogger.LogLevel = level
	return &newlogger
}

// Info print info
func (l *Gormlogger) Info(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= ormlog.Info {
		l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Warn print warn messages
func (l *Gormlogger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= ormlog.Warn {
		l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Error print error messages
func (l *Gormlogger) Error(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= ormlog.Error {
		l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Trace print sql message
//
//nolint:cyclop
func (l *Gormlogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.LogLevel <= ormlog.Silent {
		return
	}

	elapsed := time.Since(begin)
	switch {
	case err != nil && l.LogLevel >= ormlog.Error && (!errors.Is(err, ormlog.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
		sql, rows := fc()
		if rows == -1 {
			l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= ormlog.Warn:
		sql, rows := fc()
		slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
		if rows == -1 {
			l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case l.LogLevel == ormlog.Info:
		sql, rows := fc()
		if rows == -1 {
			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	}
}
